package core;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;

import org.json.JSONException;
import org.json.JSONObject;

public class NodeScanner extends Thread{
	Connection conn;
	Statement stmt;
	ResultSet rs;
	private volatile boolean done = false;
	
	public NodeScanner(){
		try {
			/* Load Connector/J driver */
			Class.forName("com.mysql.jdbc.Driver").newInstance();

			/* Establish MySQL connection */
			conn = DriverManager.getConnection(
					"jdbc:mysql://toyhouse.cc:3306/tetra?useUnicode=true&characterEncoding=utf8", "tetra", "tetra");
			
			/* Execute SQL queries */
			stmt = conn.createStatement();
			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public void run(){
		int i=0;
		while(!done){
			// Recursive update hostList and distance matrix
			try {
				i++;
				Node.NodeList = new ArrayList<Node>();
			
				rs = stmt.executeQuery("SELECT * FROM node_alive ORDER BY rating ASC");
				while(rs.next()){
					Node.NodeList.add(new Node(rs.getString("name"),rs.getString("ip"),rs.getString("db_url"),0,rs.getFloat("rating")));
				}
				
				for(Node n : Node.NodeList){
					new Ping(this, n.ip, i).run();
				}
				
				if(i>1) i = 0;
				
				//System.out.println("One ROUND SCAN FINISHED, Sleep 60 seconds");
				sleep(60000);
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		this.close();
	}
	
	void close() {	
		try {
			rs.close();
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public void setDone(){
		this.done = true;
	}
}

class Ping extends Thread{
	NodeScanner scanner;
	String ip;
	int count;
	Ping(NodeScanner scanner, String ip, int i){
		this.scanner = scanner;
		this.ip = ip;
		count = i;
	}
	public void run(){
		try {
			
			//System.out.println("Prepare to ping: " + ip);
			
			Socket sock;
			try{
				SocketAddress socketAddress = new InetSocketAddress(ip, 12357);
				sock = new Socket();
				sock.connect(socketAddress, 1000);
				sock.setSoTimeout(20000);
				
				if(count == 1) new DistanceFetcher(this.scanner,ip,sock).run();
				
			} catch (Exception e) {
				
				scanner.stmt.executeUpdate("DELETE FROM node_alive WHERE ip = '"+ip+"'");
	        	scanner.stmt.executeUpdate("DELETE FROM node_distance WHERE ip1='"+ip+"'");
	        	scanner.stmt.executeUpdate("DELETE FROM node_distance WHERE ip2='"+ip+"'");
	        	scanner.stmt.executeUpdate("INSERT INTO node_reg_history VALUES ('"+ip+"','Offline','"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+"')");
				//e.printStackTrace();
			} 
			
		} catch (Exception e) {
			e.printStackTrace();
		}
        
	}
}

class DistanceFetcher extends Thread{
	NodeScanner scanner;
	String ip;
	Socket sock;
	DistanceFetcher(NodeScanner scanner, String ip, Socket sock){
		this.scanner = scanner;
		this.ip = ip;
		this.sock = sock;
	}
	@SuppressWarnings("unchecked")
	public void run(){
		
		//System.out.println("Distance Check");
		
		JSONObject task = new JSONObject();
		ArrayList<String> theHostIP = new ArrayList<String>();
		for(Node n:Node.NodeList){
			if(n.ip!=ip) theHostIP.add(n.ip);
		}
		try {
			task.put("id", UUID.randomUUID().toString());
			task.put("type", "Ping");
			task.put("hostList", theHostIP);
		} catch (JSONException e1) {
			e1.printStackTrace();
		}

		try{
			BufferedReader is = new BufferedReader(new InputStreamReader(sock.getInputStream(),"8859_1"));
			OutputStreamWriter osw = new OutputStreamWriter(sock.getOutputStream(),"8859_1");
			
			osw.write(task.toString() + "\r\n");
			osw.flush();
			JSONObject reply = new JSONObject(is.readLine());
			sock.close();
			
			JSONObject host_list = reply.getJSONObject("hostList");
			String awayIp;
			double dist;
			Node.getNodeByIp(ip).clearDist();
			
			for(java.util.Iterator<String> iter = host_list.keys(); iter.hasNext(); ){
				awayIp = iter.next();
				dist = host_list.getDouble(awayIp);
				
				Node.getNodeByIp(ip).updateDist(Node.getNodeByIp(awayIp), dist);
				
				try {
					ResultSet rs = this.scanner.stmt.executeQuery("SELECT * FROM node_distance WHERE ip1='"+ip+"' AND ip2='"+awayIp+"'");
					if(rs.next()){
						this.scanner.stmt.executeUpdate("UPDATE node_distance SET distance="+dist+" WHERE ip1='"+ip+"' AND ip2='"+awayIp+"'");
					} else {
						this.scanner.stmt.executeUpdate("INSERT INTO node_distance VALUES ('"+ip+"', '"+awayIp+"', "+dist+")");
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		} catch (IOException e){
			e.printStackTrace();
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}
	
}